/**
 * Copyright (c) 2014.
 * Private license for beyondts.
 * Contact beyondts for using, beyondts999@163.com
 */
package net.beyondts.mplug.utils;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;

import java.io.*;
import java.text.ParseException;
import java.util.*;

/**
 * Project: beyondts-basic<br/>
 * Description: <br/>
 * Created at: 2014年8月16日<br/>
 * Owned by beyondts.
 * 
 * @author blazecrystal
 *
 * @version 1.0.0
 */
public final class Properties {
    private static final String PREFIX_PARAM = "${";

    private static final String SUFFIX_PARAM = "}";

    /**
     * 此前缀表示该属性值是被加过密的。
     */
    private static final String PREFIX_ENCRYPT = "{enc}";

    /**
     * 默认的解密密钥。
     */
    private static final String DEFAULT_ENCRYPT_KEY = "beyondts#$%@BEYONDTS.com";

    /**
     * 属性集。
     */
    private java.util.Properties properties;

    private static String encryptKey;

    /**
     * 加载一个属性文件.
     * 
     * @param filepath String 文件路径，如果是classpath下的文件，应在前面加上“classpath:”
     * @return Properties 该属性文件中记载的属性集
     * @throws IOException ...
     */
    public static Properties load(String filepath) throws IOException {
        return new Properties(filepath);
    }

    /**
     * 加载一个属性文件.
     * 
     * @param filepath String 文件路径，如果是classpath下的文件，应在前面加上“classpath:”
     * @param locale Locale 地区标识
     * @return Properties 该属性文件中记载的属性集
     * @throws IOException ...
     */
    public static Properties load(String filepath, Locale locale)
            throws IOException {
        return new Properties(filepath, locale);
    }

    /**
     * 加载一个属性文件.
     * 
     * @param filepath String 文件路径，如果是classpath下的文件，应在前面加上“classpath:”
     * @param locale String 地区标识
     * @return Properties 该属性文件中记载的属性集
     * @throws IOException ...
     */
    public static Properties load(String filepath, String locale)
            throws IOException {
        return new Properties(filepath, locale);
    }

    /**
     * 加载属性文件.
     * 
     * @param file File 属性文件
     * @return Properties 该属性文件中记载的属性集
     * @throws IOException ...
     */
    public static Properties load(File file) throws IOException {
        return new Properties(file);
    }

    /**
     * 从一个java.util.Properties加载当前类的实例。
     * 
     * @param src {@link java.util.Properties} 已存在的属性列表
     * @return {@link Properties} 得到的属性列表
     */
    public static Properties load(java.util.Properties src) {
        return new Properties(src);
    }

    /**
     * 创建一个空的属性文件.
     * 
     * @return Properties 该属性文件中记载的属性集（空）
     */
    public static Properties emptyProperties() {
        return new Properties();
    }

    /**
     * 
     * 构造函数.
     * 
     */
    private Properties() {
        this.properties = new java.util.Properties();
    }

    /**
     * 
     * 构造函数.
     * 
     * @param filepath String 包形式的属性文件名
     * @throws IOException ...
     */
    private Properties(String filepath) throws IOException {
        this.properties = new java.util.Properties();
        InputStream inputStream = this.getClass().getClassLoader()
                .getResourceAsStream(filepath);
        if (inputStream != null) {
            this.properties.load(inputStream);
        } else {
            this.properties = null;
        }
    }

    //    /**
    //     * 转换包形式的属性文件名为URI.
    //     * 
    //     * @param filename String 包形式的属性文件名
    //     * @return String 对应的URI
    //     */
    //    private String getFilename(String filename) {
    //        String name = filename.replace(".", File.separator);
    //        name += ".properties";
    //        return name;
    //    }

    /**
     * 
     * 构造函数.
     * 
     * @param filename String 包形式的属性文件名
     * @param locale Locale 地区标识
     * @throws IOException ...
     */
    private Properties(String filename, Locale locale) throws IOException {
        this.properties = new java.util.Properties();
        InputStream inputStream = null;
        try {
            inputStream = this.getClass().getClassLoader()
                    .getResourceAsStream(getFilename(filename, locale));
            if (inputStream != null) {
                this.properties.load(inputStream);
            } else {
                this.properties = null;
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }

    /**
     * 
     * 构造函数.
     * 
     * @param file File 属性文件
     * @throws IOException ...
     */
    private Properties(File file) throws IOException {
        this.properties = new java.util.Properties();
        InputStream is = null;
        try {
            is = new FileInputStream(file);
            if (file != null && file.isFile()) {
                this.properties.load(is);
            }
        } finally {
            if (is != null) {
                is.close();
            }
        }
    }

    /**
     * 转换包形式的属性文件名为URI.
     * 
     * @param filename String 包形式的属性文件名
     * @param locale Locale 地区标识
     * @return String 对应的URI
     */
    private String getFilename(String filename, Locale locale) {
        return getFilename(filename, locale.toString());
    }

    /**
     * 
     * 构造函数.
     * 
     * @param filename String 包形式的属性文件名
     * @param locale String 地区标识
     * @throws IOException ...
     */
    private Properties(String filename, String locale) throws IOException {
        this.properties = new java.util.Properties();
        InputStream inputStream = null;
        try {
            inputStream = this.getClass().getClassLoader()
                    .getResourceAsStream(getFilename(filename, locale));
            if (inputStream != null) {
                this.properties.load(inputStream);
            } else {
                this.properties = null;
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }

    /**
     * 转换包形式的属性文件名为URI.
     * 
     * @param filename String 包形式的属性文件名
     * @param locale String 地区标识
     * @return String 对应的URI
     */
    private String getFilename(String filename, String locale) {
        String name = filename.replace(".", File.separator);
        name += "_" + locale + ".properties";
        return name;
    }

    private Properties(java.util.Properties src) {
        this.properties = src;
    }

    /**
     * 用指定的键在此属性列表中搜索属性.
     * 
     * @param key String 属性键
     * @return String 属性列表中具有指定键值的值
     */
    public String getProperty(String key) {
        String value = this.properties.getProperty(key);
        return parse(value);
    }

    /**
     * 用指定的键在此属性列表中搜索属性.
     * 
     * @param key String 属性键
     * @param defaultValue Strng 默认值，当属性集中无对应的内容时，返回该默认值
     * @return String 属性列表中具有指定键值的值
     */
    public String getProperty(String key, String defaultValue) {
        String value = this.properties.getProperty(key, defaultValue);
        return parse(value);
    }

    private String parse(String value) {
        String result = "";
        int startIndex = value.indexOf(PREFIX_PARAM);
        int endIndex = value.indexOf(SUFFIX_PARAM);
        if (endIndex > startIndex) {
            String tmpKey = value.substring(startIndex + PREFIX_PARAM.length(),
                    endIndex).trim();
            String tmpValue = this.properties.getProperty(tmpKey);
            if (StringUtils.isEmpty(tmpValue)) {
                tmpValue = System.getProperty(tmpKey);
            }
            if (startIndex > 0) {
                result += value.substring(0, startIndex);
            }
            result += tmpValue;
            if (endIndex < value.length() - 1) {
                result += value.substring(endIndex + SUFFIX_PARAM.length());
            }
            value = parse(result);
        }
        return value;
    }

    /**
     * 用指定的键在此属性列表中搜索int类型的属性.
     * 
     * @param key String 属性键
     * @return int 属性列表中具有指定键值的值
     */
    public int getIntProperty(String key) {
        int value = 0;
        String tmp = getProperty(key);
        if (!StringUtils.isEmpty(tmp)) {
            try {
                value = Integer.parseInt(tmp);
            } catch (NumberFormatException ex) {

            }
        }
        return value;
    }

    /**
     * 用指定的键在此属性列表中搜索int类型的属性.
     * 
     * @param key String 属性键
     * @param defaultValue int 默认值，当属性集中无对应的内容时，返回该默认值
     * @return int 属性列表中具有指定键值的值
     */
    public int getIntProperty(String key, int defaultValue) {
        int value = defaultValue;
        String tmp = getProperty(key);
        if (!StringUtils.isEmpty(tmp)) {
            try {
                value = Integer.parseInt(tmp);
            } catch (NumberFormatException ex) {

            }
        }
        return value;
    }

    /**
     * 用指定的键在此属性列表中搜索long类型的属性.
     * 
     * @param key String 属性键
     * @return long 属性列表中具有指定键值的值
     */
    public long getLongProperty(String key) {
        long value = 0L;
        String tmp = getProperty(key);
        if (!StringUtils.isEmpty(tmp)) {
            try {
                value = Long.parseLong(tmp);
            } catch (NumberFormatException ex) {
                value = 0L;
            }
        }
        return value;
    }

    /**
     * 用指定的键在此属性列表中搜索long类型的属性.
     * 
     * @param key String 属性键
     * @param defaultValue long 默认值，当属性集中无对应的内容时，返回该默认值
     * @return long 属性列表中具有指定键值的值
     */
    public long getLongProperty(String key, long defaultValue) {
        long value = defaultValue;
        String tmp = getProperty(key);
        if (!StringUtils.isEmpty(tmp)) {
            try {
                value = Long.parseLong(tmp);
            } catch (NumberFormatException ex) {

            }
        }
        return value;
    }

    /**
     * 用指定的键在此属性列表中搜索boolean类型的属性.
     * 
     * @param key String 属性键
     * @return boolean 属性列表中具有指定键值的值
     */
    public boolean getBooleanProperty(String key) {
        boolean value = false;
        String tmp = getProperty(key);
        if (!StringUtils.isEmpty(tmp)) {
            value = Boolean.parseBoolean(tmp);
        }
        return value;
    }

    /**
     * 用指定的键在此属性列表中搜索boolean类型的属性.
     * 
     * @param key String 属性键
     * @param defaultValue boolean 默认值，当属性集中无对应的内容时，返回该默认值
     * @return boolean 属性列表中具有指定键值的值
     */
    public boolean getBooleanProperty(String key, boolean defaultValue) {
        boolean value = defaultValue;
        String tmp = getProperty(key);
        if (!StringUtils.isEmpty(tmp)) {
            value = Boolean.parseBoolean(tmp);
        }
        return value;
    }

    /**
     * 用指定的键在此属性列表中搜索date类型的属性.
     * 
     * @param key {@link String} 属性键
     * @param format {@link String} 日期格式
     * @return {@link Date} 属性列表中具有指定键值的值
     */
    public Date getDateProperty(String key, String format) {
        Date value = new Date();
        String tmp = getProperty(key);
        if (!StringUtils.isEmpty(tmp)) {
            try {
                value = DateUtils.parseDate(tmp, new String[] { format });
            } catch (ParseException ex) {

            }
        }
        return value;
    }

    /**
     * 取得Properties中的数组属性。
     * Properties中的格式如：keyPrefix.1=xx
     *                      keyPrefix.2=yy
     *                      keyPrefix.3=zz
     * 
     * @param keyPrefix {@link String} key前缀
     * @return {@link String}[] 数组属性值
     */
    public String[] getArrayProperty(String keyPrefix) {
        List<String> names = new ArrayList<String>();
        Enumeration<?> keys = this.properties.propertyNames();
        while (keys.hasMoreElements()) {
            String key = (String) keys.nextElement();
            if (key.contains(keyPrefix)) {
                names.add(key);
            }
        }
        String[] values = new String[names.size()];
        for (int i = 0; i < names.size(); i++) {
            String key = names.get(i);
            values[i] = getProperty(key);
        }
        return values;
    }

    /**
     * 用指定的键在此属性列表中搜索int类型的属性.
     * 
     * @param key {@link String} 属性键
     * @param format {@link String} 日期格式
     * @param defaultValue {@link Date} 默认值，当属性集中无对应的内容时，返回该默认值
     * @return {@link Date} 属性列表中具有指定键值的值
     */
    public Date getDateProperty(String key, String format, Date defaultValue) {
        Date value = defaultValue;
        String tmp = getProperty(key);
        if (!StringUtils.isEmpty(tmp)) {
            try {
                value = DateUtils.parseDate(tmp, new String[] { format });
            } catch (ParseException ex) {

            }
        }
        return value;
    }

    /**
     * 设置消息内容.
     * 
     * @param key String 属性键
     * @param value String 属性值
     * @return String 旧的属性值，如果原本不存在，则返回null
     */
    public String setProperty(String key, String value) {
        return (String) this.properties.setProperty(key, value);
    }

    /**
     * 移除属性集中的指定键对应的属性.
     * 
     * @param key String 属性键
     */
    public void removeProperty(String key) {
        this.properties.remove(key);
    }

    /**
     * 将该属性集输出到指定的输出流.
     * 
     * @param out OutputStream 指定的输出流
     * @param comments String 注释
     * @throws IOException ...
     */
    public void save(OutputStream out, String comments) throws IOException {
        this.properties.store(out, comments);
    }

    /**
     * 判断该属性集是否为空（文件不存在或无内容）.
     * 
     * @return boolean 如果为空则返回true，否则返回false
     */
    public boolean isEmpty() {
        boolean empty = false;
        if (this.properties == null) {
            empty = true;
        } else {
            empty = this.properties.isEmpty();
        }
        return empty;
    }

    /**
     * 属性集中所有键的列表.
     * 
     * @return List&lt;String&gt; 属性集中所有键的列表
     */
    public List<String> propertyNames() {
        List<String> nameList = new ArrayList<String>();
        Enumeration<?> names = this.properties.propertyNames();
        while (names.hasMoreElements()) {
            nameList.add((String) names.nextElement());
        }
        return nameList;
    }

    /**
     * 获得源java.util.Properties.
     * 
     * @return {@link java.util.Properties} 源
     */
    public java.util.Properties getSource() {
        return this.properties;
    }

    /**
     * getter method for "encryptKey".
     * 
     * @return the encryptKey
     */
    public static String getEncryptKey() {
        if (StringUtils.isBlank(encryptKey)) {
            encryptKey = DEFAULT_ENCRYPT_KEY;
        }
        return encryptKey;
    }

    /**
     * setter method for "encryptKey".
     * 
     * @param encryptKey the encryptKey to set
     */
    public static void setEncryptKey(String encryptKey) {
        Properties.encryptKey = encryptKey;
    }
}
